#include "TcpSocket.hpp"

using namespace std;



Network::TcpSocket::TcpSocket() throw(SocketException)
{
	_data = new Data;
	_data->instance = 1;
	_data->sock = INVALID_SOCKET;
	_data->localAddr = NULL;
	_data->remoteAddr = NULL;
}



Network::TcpSocket::TcpSocket(const SOCKET& sock, const SOCKADDR* sockaddr)
{
	_data = new Data;
	_data->instance = 1;
	_data->sock = sock;
	
	SOCKADDR_IN addr;
	socklen_t len = sizeof(addr);
	
	if(getsockname(sock, (SOCKADDR*)&addr, &len) >= 0)
		_data->localAddr = new IpEndPoint((SOCKADDR*)(&addr));
	else
		_data->localAddr = NULL;
	
	_data->remoteAddr = new IpEndPoint(sockaddr);
}



Network::TcpSocket::TcpSocket(const Network::TcpSocket& other)
 : _data(other._data)
{
	++_data->instance;
}



Network::TcpSocket::~TcpSocket()
{
    if(_data->instance == 1)
    {
    	finish();
    	
    	if(_data->localAddr != NULL)
			delete _data->localAddr;
		
		if(_data->localAddr != NULL)
			delete _data->remoteAddr;
		
		delete _data;
    }
    else
    {
    	--_data->instance;
    }
}



void Network::TcpSocket::connect(const IpEndPoint& host) throw(SocketException)
{
	if(_data->sock != INVALID_SOCKET)
		finish();
	
	if(host.type() == IpEndPoint::IPV4)
		_data->sock = ::socket(AF_INET, SOCK_STREAM, 0);
	else
		_data->sock = ::socket(AF_INET6, SOCK_STREAM, 0);
	
	if(_data->sock == INVALID_SOCKET)
		throw SocketException(lastError());
	
	if(::connect(_data->sock, host.sockaddr(), host.sockaddrSize()) < 0)
		throw SocketException(lastError());
	
	if(_data->localAddr != NULL)
    	delete _data->localAddr;
	
	if(_data->remoteAddr != NULL)
    	delete _data->remoteAddr;
		
	SOCKADDR_IN addr;
	socklen_t len = sizeof(addr);
	
	if(getsockname(_data->sock, (SOCKADDR*)&addr, &len) >= 0)
		_data->localAddr = new IpEndPoint((SOCKADDR*)&addr);
	else
		_data->localAddr = NULL;
	
	_data->remoteAddr = new IpEndPoint(host);
}


/*
void Network::TcpSocket::bind(unsigned short port) throw(SocketException)
{
    memset(&_sockaddr, 0, sizeof(_sockaddr));
    _sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);        // Adresse IP automatique
    _sockaddr.sin_family = AF_INET;                       // Protocole IP
    _sockaddr.sin_port = htons(port);                     // Port du serveur
    _socklen = sizeof(_sockaddr);
    
    
    if(::bind(_sock, (SOCKADDR*)&_sockaddr, _socklen) < 0)
        throw SocketExif(_data->localAddr != NULL)
				delete _data->localAddr;
			
			if(_data->localAddr != NULL)
				delete _data->remoteAddr;
			
			delete _data;ception(lastError());
}
*/


void Network::TcpSocket::bind(const IpEndPoint& host) throw(SocketException)
{
	if(_data->sock != INVALID_SOCKET)
		finish();
	
	if(host.type() == IpEndPoint::IPV4)
		_data->sock = ::socket(AF_INET, SOCK_STREAM, 0);
	else
		_data->sock = ::socket(AF_INET6, SOCK_STREAM, 0);
	
	if(_data->sock == INVALID_SOCKET)
		throw SocketException(lastError());
	
    if(::bind(_data->sock, host.sockaddr(), host.sockaddrSize()) < 0)
        throw SocketException(lastError());
    
    _data->localAddr = new IpEndPoint(host);
    
    if(_data->remoteAddr != NULL)
    {
    	delete _data->remoteAddr;
    	_data->remoteAddr = NULL;
	}
}



void Network::TcpSocket::listen(unsigned int backlog) throw(SocketException)
{
    if(::listen(_data->sock, backlog) < 0)
        throw SocketException(lastError());
}



Network::TcpSocket Network::TcpSocket::accept() throw(SocketException)
{
    SOCKET csock;
    SOCKADDR_IN csockaddr;
    socklen_t csocklen = sizeof(csockaddr);
    
    csock = ::accept(_data->sock, (SOCKADDR*)&csockaddr, &csocklen);
    
    if(csock == INVALID_SOCKET)
        throw SocketException(lastError());
    
    return TcpSocket(csock, (SOCKADDR*)&csockaddr);
}



int Network::TcpSocket::send(const void* data, int len, int flags) throw(SocketException)
{
	#ifdef _WIN32
		int res = ::send(_data->sock, static_cast<const char*>(data), len, flags);
	#else
		int res = ::send(_data->sock, data, len, flags | MSG_NOSIGNAL);
	#endif

	if(res <= 0)
	{
		if(res < 0)
			throw SocketException(lastError());
		else
			throw SocketException("broken connection while sending data");
	}
	
	return (int)res;
}



int Network::TcpSocket::recv(void* data, int len, int flags) throw(SocketException)
{
	#ifdef _WIN32
		int res = ::recv(_data->sock, static_cast<char*>(data), len, flags);
	#else
		int res = ::recv(_data->sock, data, len, flags | MSG_NOSIGNAL);
	#endif
	
	if(res <= 0)
	{
		if(res < 0)
			throw SocketException(lastError());
		else
			throw SocketException("broken connection while receiving data");
	}
	
	return (int)res;
}



void Network::TcpSocket::sendInt4(int data) throw(SocketException)
{
    int wrote = 0;
    data = htonl(data);
	
	while(wrote < 4)
		wrote += send(reinterpret_cast<const void*>(&data), 4-wrote, 0);
}



void Network::TcpSocket::sendInt2(short data) throw(SocketException)
{
    int wrote = 0;
    data = htons(data);
	
	while(wrote < 2)
		wrote += send(reinterpret_cast<const void*>(&data), 2-wrote, 0);
}



void Network::TcpSocket::sendChar(char data) throw(SocketException)
{
	while(send(static_cast<const void*>(&data), 1, 0) < 1);
}



int Network::TcpSocket::recvInt4() throw(SocketException)
{
    int received = 0;
    int data;
    
    while(received < 4)
        received += recv(reinterpret_cast<void*>(&data), 4, 0);
    
    return ntohl(data);
}



short Network::TcpSocket::recvInt2() throw(SocketException)
{
    int received = 0;
    short data;
    
    while(received < 4)
        received += recv(reinterpret_cast<void*>(&data), 2, 0);
    
    return ntohs(data);
}



char Network::TcpSocket::recvChar() throw(SocketException)
{
    char data;
    
    recv(static_cast<void*>(&data), 1, 0);
    
    return data;
}



void Network::TcpSocket::send(const Network::ByteArray& data) throw(SocketException)
{
	int wrote = 0;
	
	while(wrote < data.size())
		wrote += send(static_cast<const void*>(&(data[wrote])), data.size()-wrote, 0);
}



void Network::TcpSocket::recv(Network::ByteArray& data) throw(SocketException)
{
	int availableBytes = available();
	
	if(availableBytes > 0)
	{
		data.resize(availableBytes);
		int recvBytes = recv((void*)&(data[0]), availableBytes, 0);
		data.resize(recvBytes);
	}
	else
	{
		int buffSize = 4096;
		
		int res;
		socklen_t reslen = sizeof(int);

		#ifdef _WIN32
			if(::getsockopt(_data->sock, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char*>(&res), &reslen) >= 0)
				buffSize = res;
		#else
			if(::getsockopt(_data->sock, SOL_SOCKET, SO_RCVBUF, static_cast<void*>(&res), &reslen) >= 0)
				buffSize = res;
		#endif

		data.resize(buffSize);
		int recvBytes = recv((void*)&(data[0]), buffSize, 0);
		data.resize(recvBytes);
	}
}



void Network::TcpSocket::recv(Network::ByteArray& data, int len) throw(SocketException, ArgumentException)
{
	if(len <= 0)
		throw ArgumentException("'len' argument must be strictly positive");
	
	data.resize(len);
	
	int received = 0;
	
	try
	{
	    while(received < len)
		    received += recv((void*)&(data[received]), len-received, 0);
	}
	catch(std::exception&)
	{
		// Si aucune donnée n'a été reçu, on redégage l'exception
		if(received == 0)
			throw;
		else
		    data.resize(received);
	}
}



void Network::TcpSocket::recv(Network::ByteArray& data, const Network::ByteArray& separator) throw(SocketException, ArgumentException)
{
	if(separator.size() <= 0)
		throw ArgumentException("'separator' argument size must be strictly positive");
	
	const int sepSize = separator.size();
	const int buffSize = 1024;
	ByteArray buff(buffSize);
	int pos;
	int recvBytes;
	
	data.resize(0);
	
	try
	{
		do
		{
			recvBytes = recv((void*)&(buff[0]), buffSize, MSG_PEEK);
			
			int lastSize = data.size();
			data.resize(lastSize + recvBytes);
			memcpy((void*)&(data[lastSize]), (void*)&(buff[0]), recvBytes);
			
			int startingPos = lastSize - sepSize;
			if(startingPos > 0)
				pos = data.find(separator, startingPos);
			else
				pos = data.find(separator, 0);
			
			if(pos == -1)
				while(recvBytes > 0)
					recvBytes -= recv((void*)&(buff[0]), recvBytes);
		}
		while(pos == -1);
		
		int lastRecvBytes = recvBytes - (data.size() - pos - sepSize);
		while(lastRecvBytes > 0)
			lastRecvBytes -= recv((void*)&(buff[0]), lastRecvBytes);
		
		data.resize(pos);
	}
	catch(std::exception&)
	{
		// Si aucune donnée n'a été reçu, on redégage l'exception
		if(data.size() == 0)
			throw;
	}
}



void Network::TcpSocket::peek(Network::ByteArray& data) throw(SocketException)
{
	int availableBytes = available();
	
	if(availableBytes > 0)
	{
		data.resize(availableBytes);
		int recvBytes = recv((void*)&(data[0]), availableBytes, MSG_PEEK);
		data.resize(recvBytes);
	}
	else
	{
		int buffSize = 4096;
		
		int res;
		socklen_t reslen = sizeof(int);
		
		#ifdef _WIN32
			if(getsockopt(_data->sock, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char*>(&res), &reslen) >= 0)
				buffSize = res;
		#else
			if(getsockopt(_data->sock, SOL_SOCKET, SO_RCVBUF, static_cast<void*>(&res), &reslen) >= 0)
				buffSize = res;
		#endif
		
		data.resize(buffSize);
		int recvBytes = recv((void*)&(data[0]), buffSize, MSG_PEEK);
		data.resize(recvBytes);
	}
}



void Network::TcpSocket::peek(ByteArray& data, int len) throw(SocketException, ArgumentException)
{
	if(len <= 0)
		throw ArgumentException("'len' argument must be strictly positive");
	
	data.resize(len);
	
	int received = 0;
	
	try
	{
	    while(received < len)
		    received += recv((void*)&(data[received]), len, MSG_PEEK);
	}
	catch(std::exception&)
	{
		// Si aucune donnée n'a été reçu, on redégage l'exception
		if(received == 0)
			throw;
		else
		    data.resize(received);
	}
}



void Network::TcpSocket::finish()
{
	if(_data->sock != INVALID_SOCKET)
	{
		::shutdown(_data->sock, 2);
		
		#ifdef _WIN32
			::closesocket(_data->sock);
		#else
			::close(_data->sock);
		#endif

		_data->sock = INVALID_SOCKET;
	}
}



long Network::TcpSocket::available() throw(SocketException)
{
    #ifdef _WIN32
        DWORD nbBytes = 0;
        
        if(ioctlsocket(_data->sock, FIONREAD, &nbBytes) != 0)
            throw SocketException(lastError());
        
        return (long)nbBytes;
    #else
        size_t nbBytes = 0;
        
        if(ioctl(_data->sock, FIONREAD, (char*)&nbBytes) < 0)
            throw SocketException(lastError());
        
        return (long)nbBytes;
    #endif
}



bool Network::TcpSocket::finished() throw(SocketException)
{
    if(_data->sock == INVALID_SOCKET)
        return true;
    
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(_data->sock, &rfds);
    
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    
    // La fonction select renvoi 0 si il n'y a rien à lire sur 
    // la socket et un code supérieur à zéro si la connexion a 
    // été rompue ou si il y a des données à lire sur la socket
    int retval = select(_data->sock+1, &rfds, NULL, NULL, &tv);
    
    // Si il n'y a rien à lire sur la socket
    if(retval == 0)
        return false;
    
    // Si la fonction select a échouée
    else if(retval == -1)
        throw SocketException(lastError());
    
    // Si il y a réelement des données à lire.
    // Tant qu'il y a des données à lire la 
    // connexion est considérée comme non-rompue.
    else if(available() > 0)
        return false;
    
    // Sinon la connexion a été rompue
    else
        return true;
}



const Network::IpEndPoint& Network::TcpSocket::localEndPoint() const throw(SocketException)
{
	if(_data->localAddr != NULL)
		return *_data->localAddr;
	else
		throw SocketException("unable to find local end point of the socket");
}



const Network::IpEndPoint& Network::TcpSocket::remoteEndPoint() const throw(SocketException)
{
	if(_data->remoteAddr != NULL)
		return *_data->remoteAddr;
	else
		throw SocketException("unable to find remote end point of the socket");
}



Network::TcpSocket& Network::TcpSocket::operator=(const Network::TcpSocket& other)
{
	if(&other != this)
	{
		if(_data->instance == 1)
		{
			finish();
			
			if(_data->localAddr != NULL)
				delete _data->localAddr;
			
			if(_data->localAddr != NULL)
				delete _data->remoteAddr;
			
			delete _data;
		}
		else
		{
			--_data->instance;
		}
		
		_data = other._data;
		++_data->instance;
    }
	
	return *this;
}



string Network::TcpSocket::lastError()
{
    #ifdef _WIN32
        char buff[256] = {'\0'};
        int error = WSAGetLastError();
        FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM,
                        NULL, error, 0,
                        buff, 256,
                        NULL);
        return string(buff);
    #else
        return string(strerror(errno));
    #endif
}




